package net.yxsoft.quorum;


import com.jfinal.kit.PropKit;
import com.jfinal.kit.Ret;
import com.jfinal.log.Log;
import com.jfinal.plugin.activerecord.ActiveRecordException;
import com.jfinal.plugin.activerecord.Db;
import com.jfinal.plugin.activerecord.Model;
import com.jfinal.plugin.activerecord.Record;
import com.jfinal.plugin.ehcache.CacheKit;
import net.yxsoft.kit.CustomDb;
import net.yxsoft.kit.RSAKit;
import net.yxsoft.kit.SM234Util;
import net.yxsoft.resource.Res;
import net.yxsoft.resource.RetRes;
import net.yxsoft.service.BlockService;
import org.web3j.abi.*;
import org.web3j.abi.datatypes.*;
import org.web3j.abi.datatypes.generated.Uint256;
import org.web3j.abi.datatypes.generated.Uint32;
import org.web3j.crypto.*;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.admin.Admin;
import org.web3j.protocol.admin.methods.response.NewAccountIdentifier;
import org.web3j.protocol.admin.methods.response.PersonalUnlockAccount;
import org.web3j.protocol.core.DefaultBlockParameter;
import org.web3j.protocol.core.DefaultBlockParameterName;
import org.web3j.protocol.core.RemoteFunctionCall;
import org.web3j.protocol.core.methods.request.Transaction;
import org.web3j.protocol.core.methods.response.*;
import org.web3j.protocol.http.HttpService;
import org.web3j.quorum.Quorum;
import org.web3j.quorum.methods.request.PrivateTransaction;

import org.web3j.tuples.generated.Tuple4;
import org.web3j.tuples.generated.Tuple6;
import org.web3j.tx.Transfer;
import org.web3j.utils.Convert;
import org.web3j.utils.Numeric;
import rapid.util.DateKit;
import rapid.util.StrKit;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.*;
import java.util.concurrent.ExecutionException;

import static java.lang.Thread.sleep;

public class QuorumKit {


    public static final String contractAddress = "0x4c9b0fc63c226437e95f6492649f4781df386929";
    public static final String fromAccount = "0x62cb2727a224fb83b992018c4ec546eae1024c3d";
    public static final String privateKey = "8d90a70d251c46091f0e699a4eaed537cd26b55457390f21cbd28b3454ef3d45";
    // GAS
    public static BigInteger GAS_PRICE = BigInteger.valueOf(0);
    // GAS上限
    public static BigInteger GAS_LIMIT = BigInteger.valueOf(4_300_000L);
    private static Quorum quorum;
    public static Admin admin;
    private static Web3j web3j;

    private static final Log log = Log.getLog(CacheKit.class);

    static void init(Quorum qm, Admin am, Web3j web3){

        quorum = qm;
        admin = am;
        web3j = web3;
    }

    /**
     * 创建新的钱包
     * @param password
     * @return
     */
    public static Ret createdWallet(String password){

        try {
            ECKeyPair ecKeyPair = Keys.createEcKeyPair();

            BigInteger privateKeyInDec = ecKeyPair.getPrivateKey();
            BigInteger publicKeyInDec = ecKeyPair.getPublicKey();
            String privateKey = privateKeyInDec.toString(16);
            String publicKey = publicKeyInDec.toString(16);
       /*     Ret ret = RSAKit.generatorRSA();
            String privateKey= ret.get(RetRes.PRIVATE_KEY).toString();
            String publicKey=ret.get(RetRes.PUBLIC_KEY).toString();*/
            WalletFile aWallet = Wallet.createLight(password, ecKeyPair);
            String address = aWallet.getAddress();
            if (address.startsWith("0x")) {
                address = address.substring(2).toLowerCase();
            } else {
                address = address.toLowerCase();
            }
            address = "0x" + address;

            return  Ret.ok("account", address).set("privateKey",privateKey).set("publicKey",publicKey);
        } catch (InvalidAlgorithmParameterException |
                CipherException | NoSuchProviderException | NoSuchAlgorithmException e) {
            System.out.println(e.getCause().toString());
        }

        return Ret.fail();
    }

    /**
     * 创建新的钱包
     * @param password
     * @return
     */
    public static Ret createdWalletnew(String password){

        try {
            ECKeyPair ecKeyPair = Keys.createEcKeyPair();
  /*          BigInteger privateKeyInDec = ecKeyPair.getPrivateKey();
            String privateKey = privateKeyInDec.toString(16);*/
            Ret ret = RSAKit.generatorRSA();
            String privateKey= ret.get(RetRes.PRIVATE_KEY).toString();
            String publicKey=ret.get(RetRes.PUBLIC_KEY).toString();
            WalletFile aWallet = Wallet.createLight(password, ecKeyPair);
            String address = aWallet.getAddress();
            if (address.startsWith("0x")) {
                address = address.substring(2).toLowerCase();
            } else {
                address = address.toLowerCase();
            }
            address = "0x" + address;

            return  Ret.ok("account", address).set("privateKey",privateKey);
        } catch (InvalidAlgorithmParameterException |
                CipherException | NoSuchProviderException | NoSuchAlgorithmException e) {
            e.printStackTrace();
        }

        return Ret.fail();
    }

    /**
     * 创建新的钱包
     * @param password
     * @return
     */
    public static Ret createdWallet(String password,String userid,String FamilyHash) throws Exception {

        try {
            ECKeyPair ecKeyPair = Keys.createEcKeyPair();
            BigInteger privateKeyInDec = ecKeyPair.getPrivateKey();
            BigInteger publicKeyInDec = ecKeyPair.getPublicKey();
            String privateKey = privateKeyInDec.toString(16);
            String publicKey = publicKeyInDec.toString(16);
            WalletFile aWallet = Wallet.createLight(password, ecKeyPair);
            String address = aWallet.getAddress();
            if (address.startsWith("0x")) {
                address = address.substring(2).toLowerCase();
            } else {
                address = address.toLowerCase();
            }
            address = "0x" + address;

            List<Record> userlist= Db.find("select * from ts_user where id=?",userid);
            int row=0;
            if(userlist.size()==0){

                row= (int) CustomDb.updateWithExceptionHandling("insert into ts_user (id,Familyaccount_pass,trust_account,private_key,public_key) values (?,?,?,?,?) ",userid,FamilyHash,address,privateKey,publicKey).getData();
            }
            if(row>0){
                return  Ret.ok("publicKey", publicKey);
            }

        } catch (InvalidAlgorithmParameterException |
                CipherException | NoSuchProviderException | NoSuchAlgorithmException e) {
            System.out.println(e.getCause().toString());
        }






        return Ret.fail();
    }
    /**
     * 创建新账号
     int row=0;
     if(userlist.size()==0){

     row=  Db.updateWithExceptionHandling("insert into ts_user (id,Familyaccount_pass,private_key,public_key) values (?,?,?,?) ",userid,FamilyHash,SM234Util.sm4Encrypt(Res.BLOCK_SM4KeEY,ret.get(RetRes.PRIVATE_KEY).toString()),ret.get(RetRes.PUBLIC_KEY));
     }
     if(row>0){
     return  Ret.ok("publicKey", ret.get(RetRes.PUBLIC_KEY));
     * @param password
     * @return
     */
    public static String getNewAccount(String password){

        try {
            NewAccountIdentifier identifier = admin.personalNewAccount(password).send();

            return identifier.getAccountId();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return null;
    }
    /**
     * 获取当前块高
     * @return
     */
    public static BigInteger getBlockNumber(){

        try {
            EthBlockNumber blockNumber = quorum.ethBlockNumber().send();

            return blockNumber.getBlockNumber();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
    /**
     * 获取块信息
     * @param blockNumber
     * @return
     */
    public static EthBlock ethBlock(Integer blockNumber){

        try {
            return quorum.ethGetBlockByNumber(DefaultBlockParameter.valueOf(BigInteger.valueOf(blockNumber)),true).send();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
    /**
     * 根据块高获取快内交易信息
     */
    public static List<EthBlock.TransactionResult> blockTrans(Integer blockNumber){


        try {
            //获取块内交易
            EthBlock block = quorum.ethGetBlockByNumber(DefaultBlockParameter.valueOf(BigInteger.valueOf(blockNumber)),true).send();

            //块内交易明细
            List<EthBlock.TransactionResult> transactionResults;
            if(block.getResult() != null){
                transactionResults = block.getBlock().getTransactions();
//          for(EthBlock.TransactionResult et : transactionResults){
//
//            EthBlock.TransactionObject eto  = (EthBlock.TransactionObject) et;
//
//            QuorumKit.decode(eto.getInput(), prependHexPrefix);
//
//          }

                return transactionResults;
            }

        } catch (IOException e) {
            e.printStackTrace();
        }

        return null;
    }


    public static String unlock() throws IOException {

        PersonalUnlockAccount personalUnlockAccount = admin.personalUnlockAccount(PropKit.get("quorum.fromAddress"), "", BigInteger.valueOf(1000 * 60 * 1200)).send();
        return personalUnlockAccount.toString();
    }

    /**
     * 发起合约交易
     * hashValue  调用encodeCreate 方法转码
     * 其中参数attrib_str  请参考void main  attrib_01|attrib_02|attrib_03  共42个用|分割，无内容留空
     * @param hashValue
     * @return
     */
    public static String sendTransaction(String hashValue, String fromAccount, String contractAddress, String passwd){

        /*
         * 解锁账户
         */
        try {
            //    System.out.println(admin.ethBlockNumber().send());
            PersonalUnlockAccount personalUnlockAccount = admin.personalUnlockAccount(fromAccount, passwd).send();
            if (personalUnlockAccount.accountUnlocked()) {
                // send a transaction
                EthSendTransaction send = quorum.ethSendTransaction(new Transaction(fromAccount, null, GAS_PRICE, GAS_LIMIT,
                        contractAddress, BigInteger.valueOf(0x0), hashValue)).sendAsync().get();

                return send.getTransactionHash();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException | IOException e) {
            e.printStackTrace();
        }catch (Exception e){
            e.printStackTrace();
        }

        return null;
    }


    /**
     * 发起私有合约交易
     * @param hashValue
     * @param fromAccount
     * @param contractAddress
     * @param passwd
     * @param privateFor
     * @return
     */
    public static String sendPrivateTransaction(String hashValue, String fromAccount, String contractAddress,String passwd, List<String> privateFor){

        /*
         * 解锁账户
         */
        try {
            PersonalUnlockAccount personalUnlockAccount = admin.personalUnlockAccount(fromAccount, passwd).sendAsync().get();
            if (personalUnlockAccount.accountUnlocked()) {
                // send a transaction
                EthSendTransaction send = quorum.ethSendTransaction(new PrivateTransaction(fromAccount, null, GAS_LIMIT,
                        contractAddress, BigInteger.valueOf(0x0), hashValue, fromAccount, privateFor)).sendAsync().get();

                return send.getTransactionHash();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

        return null;
    }

    /**
     * 根据txHash获取交易详细信息
     * @param txHash
     * @return
     */
    public static EthTransaction ethTransactionByHash(String txHash){

        EthTransaction transaction = null;
        try {
            transaction = quorum.ethGetTransactionByHash(txHash).send();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return transaction;
    }

    public static Ret ethTransactionByHash(String txHash,String familyHash,String publicKey) throws Exception {
        Ret ret = Ret.fail();
        EthTransaction transaction = null;
        String isDecrypt="0";
        try {

            transaction = quorum.ethGetTransactionByHash(txHash).send();

            List<Type> types = QuorumKit.decode(transaction.getResult().getInput(), transaction.getResult().getInput().substring(2, 10));


            BigInteger conflictId = (BigInteger) types.get(0).getValue();
            BigInteger rowId = (BigInteger) types.get(1).getValue();
            BigInteger parRowId = (BigInteger) types.get(2).getValue();
            BigInteger created = (BigInteger) types.get(3).getValue();
            String createBy = (String) types.get(4).getValue();
            BigInteger lastUpd = (BigInteger) types.get(5).getValue();
            String lastUpdBy = (String) types.get(6).getValue();
            BigInteger modificationNum = (BigInteger) types.get(7).getValue();
            String eimSource = (String) types.get(8).getValue();
            String attribStr = (String) types.get(9).getValue();


 /*           Record family= Db.findFirst("select * from ts_user where public_key=? and Familyaccount_pass=?",publicKey,familyHash);
            if(family!=null && StrKit.notBlank(family.get("private_key"))){

               String attribStrjg= RSAKit.pkcs8EncodedKeySpec(SM234Util.sm4Decrypt(Res.BLOCK_SM4KeEY,family.get("private_key").toString()), attribStr);
                if (attribStrjg != null) {
                    isDecrypt="1";
                    attribStr=attribStrjg;
                }

            }*/

            ret = Ret.ok().set("conflictId",conflictId)
                    .set("modificationNum", modificationNum)
                    .set("createdBy",createBy)
                    .set("eimSource",eimSource)
                    .set("attribStr", attribStr).set("isDecrypt",isDecrypt);

        } catch (IOException e) {
            e.printStackTrace();
        }
        return ret;
    }


    public static Ret ethTransactionByHash(String [] txHashList,String familyHash,String publicKey) throws Exception {
        Ret ret = Ret.fail();
        List<Map> blockinfolist=new ArrayList<Map>();

        Record family= Db.findFirst("select * from ts_user where public_key=? and Familyaccount_pass=?",publicKey,familyHash);
        String privatekey="";
        if (family!=null && StrKit.notBlank(family.get("private_key"))) {
            privatekey = SM234Util.sm4Decrypt(Res.BLOCK_SM4KeEY, family.get("private_key").toString());
        }
        for (String txHash : txHashList) {
            String isDecrypt="0";
            EthTransaction transaction = null;
            Map map=new HashMap();
            try {
                transaction = quorum.ethGetTransactionByHash(txHash).send();
                List<Type> types = QuorumKit.decode(transaction.getResult().getInput(), transaction.getResult().getInput().substring(2, 10));


                BigInteger conflictId = (BigInteger) types.get(0).getValue();
                BigInteger rowId = (BigInteger) types.get(1).getValue();
                BigInteger parRowId = (BigInteger) types.get(2).getValue();
                BigInteger created = (BigInteger) types.get(3).getValue();
                String createBy = (String) types.get(4).getValue();
                BigInteger lastUpd = (BigInteger) types.get(5).getValue();
                String lastUpdBy = (String) types.get(6).getValue();
                BigInteger modificationNum = (BigInteger) types.get(7).getValue();
                String eimSource = (String) types.get(8).getValue();
                String attribStr = (String) types.get(9).getValue();

                if (family!=null && StrKit.notBlank(privatekey)) {
                    String  attribStrjg = RSAKit.pkcs8EncodedKeySpec(privatekey, attribStr);

                    if (attribStrjg != null) {
                        isDecrypt="1";
                        attribStr=attribStrjg;
                    }
                }

                map.put("conflictId", conflictId);
                map.put("modificationNum", modificationNum);
                map.put("createdBy", createBy);
                map.put("eimSource", eimSource);
                map.put("attribStr", attribStr);
                map.put("isDecrypt", isDecrypt);
                blockinfolist.add(map);

            } catch (IOException e) {
                e.printStackTrace();
            }
        }


        ret = Ret.ok().set("blockinfolist",blockinfolist);
        return ret;
    }

    /**
     * 编码转换(修正版本)
     * @param methodName
     * @param conflict_id
     * @param row_id
     * @param par_row_id
     * @param created
     * @param created_by
     * @param last_upd
     * @param last_upd_by
     * @param modification_num
     * @param eim_source
     * @param attrib_str
     * @return
     */
    public static String encodeCreate(String methodName, BigInteger conflict_id, BigInteger row_id, BigInteger par_row_id, BigInteger created,
                                      String created_by, BigInteger last_upd, String last_upd_by, BigInteger modification_num, String eim_source, String attrib_str){

        final Function function = new Function(
                methodName,
                Arrays.<Type>asList(new Uint32(conflict_id),
                        new Uint32(row_id),
                        new Uint32(par_row_id),
                        new Utf8String(created.toString()),
                        new Utf8String(created_by),
                        new Utf8String(last_upd.toString()),
                        new Utf8String(last_upd_by),
                        new Uint32(modification_num),
                        new Utf8String(eim_source),
                        new Utf8String(attrib_str)),
                Collections.<TypeReference<?>>emptyList());

        return FunctionEncoder.encode(function);
    }





    /**
     * 编码转换(新版本)
     * @param methodName
     * @param conflict_id
     * @param row_id
     * @param par_row_id
     * @param created
     * @param created_by
     * @param last_upd
     * @param last_upd_by
     * @param modification_num
     * @param eim_source
     * @param attrib_str
     * @return
     */
    public static String encodeCreate_new(String methodName, BigInteger conflict_id, BigInteger row_id, BigInteger par_row_id, String created,
                                          String created_by, String last_upd, String last_upd_by, BigInteger modification_num, String eim_source, String attrib_str){

        final Function function = new Function(
                methodName,
                Arrays.<Type>asList(new Uint32(conflict_id),
                        new Uint32(row_id),
                        new Uint32(par_row_id),
                        new Utf8String(created),
                        new Utf8String(created_by),
                        new Utf8String(last_upd),
                        new Utf8String(last_upd_by),
                        new Uint32(modification_num),
                        new Utf8String(eim_source),
                        new Utf8String(attrib_str)),
                Collections.<TypeReference<?>>emptyList());

        return FunctionEncoder.encode(function);
    }

    /**
     * 获取erc721合约对象
     * @param contractAddress
     * @return
     */
    public static MyERC721 getERC721Contract(String contractAddress, String privateKeys){

        if(StrKit.isBlank(contractAddress)){
            throw new ActiveRecordException("contractAddress can not be null");
        }

        Credentials credentials = Credentials.create(privateKeys);
        //DefaultGasProvider gasProvider = new DefaultGasProvider();
        MyERC721 erc721 = MyERC721.load(contractAddress, web3j, credentials, QuorumKit.GAS_PRICE, QuorumKit.GAS_LIMIT);

        return erc721;
    }

    /**
     * 解码erc721 input
     */
    public static Ret decodeInputToTokenId(String inputdata){

        String method = inputdata.substring(0, 10);
        String to;
        String value;
        if("0x26dd860a".equals(method)){

            method = "mintUniqueTokenTo";
            to = inputdata.substring(10, 74);
            value = inputdata.substring(74);
        }else if("0x42842e0e".equals(method)){

            method = "safeTransferFrom";
            to = inputdata.substring(74, 138);
            value = inputdata.substring(138);
        }else{

            method = "transferFrom";
            to = inputdata.substring(74, 138);
            value = inputdata.substring(138);
        }
        Method refMethod = null;
        Address address = null;
        Uint256 amount = null;

        try {
            refMethod = TypeDecoder.class.getDeclaredMethod("decode", String.class, int.class, Class.class);
            refMethod.setAccessible(true);
            address = (Address) refMethod.invoke(null, to, 0, Address.class);
            amount = (Uint256) refMethod.invoke(null, value, 0, Uint256.class);

            return Ret.ok().set("method", method).set("address", address).set("tokenId", amount.getValue());
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 编码转换
     * @param methodName
     * @param conflict_id
     * @return
     */
    public static String encodeGet(String methodName, BigInteger conflict_id){

        final Function function = new Function(
                methodName,
                Arrays.<Type>asList(new Uint32(conflict_id)),
                Collections.<TypeReference<?>>emptyList());

        return FunctionEncoder.encode(function);
    }
    /**
     * 解码
     * @param value
     * @param methodId
     * @return
     */
    public static List<Type> decode(String value, String methodId){

        final Function function = new Function(methodId,
                Collections.<Type>emptyList(),
                Arrays.<TypeReference<?>>asList(new TypeReference<Uint32>() {}, new TypeReference<Uint32>() {}, new TypeReference<Uint32>() {}, new TypeReference<Utf8String>() {}, new TypeReference<Utf8String>() {},new TypeReference<Utf8String>() {}, new TypeReference<Utf8String>() {},new TypeReference<Uint32>() {},new TypeReference<Utf8String>() {},new TypeReference<Utf8String>() {}));

        return FunctionReturnDecoder.decode(value.replace(methodId,""), function.getOutputParameters());
    }
    /**
     * 纯字符解码
     * @param value
     * @param methodId
     * @return
     */
    public static List<Type> deStrCode(String value, String methodId){

        final Function function = new Function(methodId,
                Collections.<Type>emptyList(),
                Arrays.<TypeReference<?>>asList(new TypeReference<Utf8String>() {}, new TypeReference<Utf8String>() {}, new TypeReference<Utf8String>() {}, new TypeReference<Utf8String>() {}, new TypeReference<Utf8String>() {},new TypeReference<Utf8String>() {}));

        return FunctionReturnDecoder.decode(value.replace(methodId,""), function.getOutputParameters());
    }

    public static byte getPaddingValue(NumericType numericType) {
        return (byte)(numericType.getValue().signum() == -1 ? -1 : 0);
    }

    public static String encodeNumeric(NumericType numericType) {
        byte[] rawValue = toByteArray(numericType);
        byte paddingValue = getPaddingValue(numericType);
        byte[] paddedRawValue = new byte[32];
        if (paddingValue != 0) {
            for(int i = 0; i < paddedRawValue.length; ++i) {
                paddedRawValue[i] = paddingValue;
            }
        }

        System.arraycopy(rawValue, 0, paddedRawValue, 32 - rawValue.length, rawValue.length);
        return Numeric.toHexStringNoPrefix(paddedRawValue);
    }

    public static byte[] toByteArray(NumericType numericType) {
        BigInteger value = numericType.getValue();
        if ((numericType instanceof Ufixed || numericType instanceof Uint) && value.bitLength() == 256) {
            byte[] byteArray = new byte[32];
            System.arraycopy(value.toByteArray(), 1, byteArray, 0, 32);
            return byteArray;
        } else {
            return value.toByteArray();
        }
    }

    public static String encodeString(Utf8String string) {
        byte[] utfEncoded = string.getValue().getBytes(StandardCharsets.UTF_8);
        return encodeDynamicBytes(new DynamicBytes(utfEncoded));
    }

    static String encodeDynamicBytes(DynamicBytes dynamicBytes) {
        int size = dynamicBytes.getValue().length;
        String encodedLength = TypeEncoder.encode(new Uint(BigInteger.valueOf((long)size)));
        String encodedValue = encodeBytes(dynamicBytes);
        StringBuilder result = new StringBuilder();
        result.append(encodedLength);
        result.append(encodedValue);
        return result.toString();
    }

    static String encodeBytes(BytesType bytesType) {
        byte[] value = bytesType.getValue();
        int length = value.length;
        int mod = length % 32;
        byte[] dest;
        if (mod != 0) {
            int padding = 32 - mod;
            dest = new byte[length + padding];
            System.arraycopy(value, 0, dest, 0, length);
        } else {
            dest = value;
        }

        return Numeric.toHexStringNoPrefix(dest);
    }

    public static boolean isBlank(String str) {
        if (str == null) {
            return true;
        } else {
            int len = str.length();
            if (len == 0) {
                return true;
            } else {
                int i = 0;

                while(i < len) {
                    switch(str.charAt(i)) {
                        case '\t':
                        case '\n':
                        case '\r':
                        case ' ':
                            ++i;
                            break;
                        default:
                            return false;
                    }
                }
                return true;
            }
        }
    }



    /**
     * 拼接上链模型字段为字符串 以|分割
     * @param model
     * @return
     */
    public static String attrStrM(Record model){

        StringBuffer attrStr = new StringBuffer();
        if(null != model){

            for (int i = 1; i <= 42; i++) {
                String attr = "attrib_";
                if(i <= 9){
                    attr = attr + "0" + i;
                }else{
                    attr = attr + i;
                }

                if(model.getStr(attr) != null){

                    attrStr.append(model.getStr(attr)).append("|");
                }else{
                    attrStr.append(" |");
                }
            }
        }

        return attrStr.toString();
    }

    /**
     * 拼接上链模型字段为字符串 以|分割
     * @param model
     * @return
     */
    public static String attrStr(Model model){

        StringBuffer attrStr = new StringBuffer();
        if(null != model){

            for (int i = 1; i <= 42; i++) {
                String attr = "attrib_";
                if(i <= 9){
                    attr = attr + "0" + i;
                }else{
                    attr = attr + i;
                }

                if(model.getStr(attr) != null){

                    attrStr.append(model.getStr(attr)).append("|");
                }else{
                    attrStr.append(" |");
                }
            }
        }

        return attrStr.toString();
    }


    /**
     * 分解链上数据
     * @param atstr
     * @return
     */
    public static String getAttStr(String atstr){
        StringBuffer attrStr = new StringBuffer();
        if(StrKit.notBlank(atstr)) {
            String[] ats = atstr.split("\\|");
            String fid = ats[0];

            if(fid!=null && StrKit.notBlank(fid)) {


                for (int i = 0; i < ats.length; i++) {
                    String attr = "attrib_";
                    if (i <= 9) {
                        attr = attr + "0" + (i+1);
                    } else {
                        attr = attr + (i+1);
                    }
                    if (StrKit.notBlank(ats[i])){
                        Record r = Db.findFirst("select title from fn_form_field where field=? and form_id=?", attr.toUpperCase(), fid);
                        if(r!=null && StrKit.notBlank(r.getStr("title"))){
                            attrStr.append("("+r.getStr("title")+")"+ats[i]).append("|");
                        }else{
                            if(attr.equals("attrib_01")){
                                Record r1 = Db.findFirst("select name from fn_form where  id=?", fid);
                                if(r1!=null && StrKit.notBlank(r1.getStr("name"))) {
                                    attrStr.append("(表单名称:"+r1.getStr("name")+")" + ats[i]).append("|");
                                }else{
                                    attrStr.append(ats[i]).append("|");
                                }
                            }else {
                                attrStr.append(ats[i]).append("|");
                            }
                            continue;
                        }
                    }else{
                        attrStr.append(" |");
                        continue;
                    }

                }


                return attrStr.toString();
            }else{
                return atstr;
            }
        }else{
            return atstr;
        }
    }

    /**
     * 拼接上链模型字段为字符串最多20个 以|分割
     * @param model
     * @return
     */
    public static String attrStrt(Model model){

        StringBuffer attrStr = new StringBuffer();
        if(null != model){

            for (int i = 1; i <= 20; i++) {
                String attr = "attrib_";
                if(i <= 9){
                    attr = attr + "0" + i;
                }else{
                    attr = attr + i;
                }

                if(model.getStr(attr) != null){

                    attrStr.append(model.getStr(attr)).append("|");
                }else{
                    attrStr.append(" |");
                }
            }
        }

        return attrStr.toString();
    }
    private static BlockService srv = BlockService.me;






    /**
     * 指定私钥签名上链
     * @param privateKey
     * @param hashValue
     * @return
     * @throws Exception
     */
    public static String sendTransactionToPk(String privateKey, String hashValue) throws Exception {

        // 签名数据
        //从私钥恢复Credentials
        Credentials credentials = Credentials.create(privateKey);
        String account = credentials.getAddress();
        String texthash="";
        while (true) {
            BigInteger currentNonce = web3j.ethGetTransactionCount(account, DefaultBlockParameterName.LATEST).send().getTransactionCount();
            currentNonce.add(BigInteger.ONE);

            RawTransaction rawTransaction = RawTransaction.createTransaction(currentNonce, QuorumKit.GAS_PRICE, QuorumKit.GAS_LIMIT, PropKit.get("quorum.contractAddress"), BigInteger.valueOf(0), hashValue);



            byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, credentials);
            String hexValue = Numeric.toHexString(signedMessage);
            // 发送数据
            EthSendTransaction response = quorum.ethSendRawTransaction(hexValue).send();

            if (!response.hasError()) {
                texthash= response.getTransactionHash();
                System.out.println("Trade Hash: " + response.getTransactionHash());
                break;
            }

            try {
                Thread.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        return texthash;
    }


    public static ModelRegistry getModelRegitryContract(String contractAddress, String privateKeys) {

        if (StrKit.isBlank(contractAddress)) {
            throw new ActiveRecordException("contractAddress can not be null");
        }

        Credentials credentials = Credentials.create(privateKeys);
        //DefaultGasProvider gasProvider = new DefaultGasProvider();
        ModelRegistry modelRegistry = ModelRegistry.load(contractAddress, web3j, credentials, QuorumKit.GAS_PRICE, QuorumKit.GAS_LIMIT);

        return modelRegistry;
    }
    public static String sendErc721(String id) throws Exception {

        ECKeyPair ecKeyPair = Keys.createEcKeyPair();
        BigInteger privateKeyInDec = ecKeyPair.getPrivateKey();
        String privateKey = privateKeyInDec.toString(16);

//        MyERC721 erc721 = QuorumKit.getERC721Contract("0x493b9006b3dbf24bcb940fcf92f79108623317f2", "554041c7a5bcd8056c9380d5c553066777da9ff7d79bc59021db850d9fea3685");
        MyERC721 erc721 = QuorumKit.getERC721Contract(PropKit.get("quorum.contractAddress"), privateKey);
        TransactionReceipt receipt = erc721.mintUniqueTokenTo(PropKit.get("quorum.contractAddress"), BigInteger.valueOf(Long.valueOf(id)), "vvvv").send();
        return receipt.getBlockHash();
    }

    public static String sendToken() throws Exception {

        //    //发送代币转账交易
        String address_to = "0x9d141134eddecee591cfcef29f64d0c715942946";

        Instant start = Instant.now();
        Credentials credentials = Credentials.create("554041c7a5bcd8056c9380d5c553066777da9ff7d79bc59021db850d9fea3685");
        Instant end = Instant.now();
        long executionTime = Duration.between(start, end).toMillis();
        System.out.println("Execution time 私钥: " + executionTime + " milliseconds");

        TransactionReceipt send = Transfer.sendFunds(web3j, credentials, address_to, BigDecimal.ONE, Convert.Unit.FINNEY).send();

        System.out.print("trans hash=" + send.getTransactionHash() + "\n");
        Instant end2 = Instant.now();
        long executionTime2 = Duration.between(end, end2).toMillis();
        System.out.println("Execution time 交易: " + executionTime2 + " milliseconds");

        return send.getTransactionHash();
    }



    public static ModelRegistry getModelRegistry(String contractAddress, String privateKeys) {

        if (StrKit.isBlank(contractAddress)) {
            throw new ActiveRecordException("contractAddress can not be null");
        }

        Credentials credentials = Credentials.create(privateKeys);
        //DefaultGasProvider gasProvider = new DefaultGasProvider();
        ModelRegistry modelRegistry = ModelRegistry.load(contractAddress, web3j, credentials, QuorumKit.GAS_PRICE, QuorumKit.GAS_LIMIT);

        return modelRegistry;
    }

    public static String sendModelRegistry(String privateKey, BigInteger conflict_id, BigInteger row_id, BigInteger par_row_id, BigInteger created,
                                           String created_by, BigInteger last_upd, String last_upd_by, BigInteger modification_num, String eim_source, String attrib_str)  {

        ModelRegistry modelRegistry = QuorumKit.getModelRegistry(PropKit.get("quorum.contractAddress"), privateKey);


        TransactionReceipt receipt = null;
        try {
            receipt = modelRegistry.createModel(conflict_id, row_id, par_row_id, created.toString(), created_by, last_upd.toString(), last_upd_by, modification_num, eim_source, attrib_str).send();
        } catch (Exception e) {
            e.printStackTrace();
        }


        RemoteFunctionCall<Tuple4<BigInteger, BigInteger, BigInteger, String>> call = modelRegistry.getModelInfo(conflict_id);

        try {
            // 发送异步调用并获取返回值
            Tuple4<BigInteger, BigInteger, BigInteger, String> result = call.send();

            // 处理返回值
            BigInteger rowid = result.component1();
            if(rowid.equals(row_id)) {

                return receipt.getBlockHash();
            }else
                return null;
        } catch (Exception e) {
            // 处理异常
            e.printStackTrace();
            return null;
        }

    }


    /**
     * @MethodName: setattrib
     * @Description: 将分割的字段填充进模型
     * @param: attrStr
     * @param: modeName
     * @return com.jfinal.plugin.activerecord.Record
     * @throws
     * @date 2023/11/1 12:54
     * @author wj
     */
    public static Record setattrib(String  attrStr,String modeName){

        Record record=new Record();
        if(StrKit.notBlank(attrStr) && StrKit.notBlank(modeName)){

            List<Record> records=Db.find("select t1.attrib_name,t1.attrib_type from ts_party t left join ts_datamodel_properties t1 on t.conflict_id=t1.model_id where t.`name`=?",modeName);
            if(records!=null && records.size()>0) {
                Map<String, String> map = new HashMap<>();
                for (Record record2 : records) {
                    String key = record2.getStr("attrib_name");
                    String value = record2.getStr("attrib_type");

                    map.put(key, value);
                }


                String[] attStrs = attrStr.split("\\|");



                for (int i = 1; i <= attStrs.length; i++) {
                    String attr = "ATTRIB_";
                    if (i <= 9) {
                        attr = attr + "0" + i;
                    } else {
                        attr = attr + i;
                    }
                    String type=map.get(attr);
                    String str = attStrs[i];

                    if (type.indexOf("int") != -1) {

                        if (str.matches("-?\\d+")) {
                            int intstr = Integer.parseInt(str);
                            record.set(attr, intstr);
                        }
                    } else if (type.indexOf("decimal") != -1) {

                        if (str.matches("-?\\d+(\\.\\d+)?")) {
                            BigDecimal decimal = new BigDecimal(str);
                            record.set(attr, decimal);
                        }
                    } else if (type.indexOf("date") != -1) {

                        DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
                        try {
                            LocalDateTime datetime = LocalDateTime.parse(str, dateFormatter);
                            record.set(attr, datetime);
                        } catch (DateTimeParseException e) {
                            System.out.println("不是一个有效的日期时间：" + str);
                            continue;
                        }
                    } else {
                        record.set(attr, str);
                    }



                }
            }
        }

        return record;
    }



}
